package org.raftkv.raft.pb;

import org.raftkv.commons.Tuple;
import org.raftkv.raft.confchange.Changer;
import org.raftkv.raft.pb.enums.ConfChangeType;
import org.raftkv.raft.tracker.Config;
import org.raftkv.raft.tracker.ProgressMap;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;

public class ConfState {

    private List<Long> voters;

    private List<Long> learners;

    private List<Long> votersOutgoing;

    private List<Long> learnersNext;

    private boolean autoLeave;

    private static Tuple<List<ConfChangeSingle>, List<ConfChangeSingle>> toConfChangeSingle(ConfState cs) {
        List<ConfChangeSingle> in = new ArrayList<>();
        List<ConfChangeSingle> out = new ArrayList<>();
        for (Long id : cs.votersOutgoing) {
            out.add(new ConfChangeSingle(ConfChangeType.ConfChangeAddNode, id));
        }

        for (Long id : cs.votersOutgoing) {
            in.add(new ConfChangeSingle(ConfChangeType.ConfChangeRemoveNode, id));
        }

        for (Long id : cs.voters) {
            in.add(new ConfChangeSingle(ConfChangeType.ConfChangeAddNode, id));
        }

        for (Long id : cs.learners) {
            in.add(new ConfChangeSingle(ConfChangeType.ConfChangeAddLearnerNode, id));
        }

        for (Long id : cs.learnersNext) {
            in.add(new ConfChangeSingle(ConfChangeType.ConfChangeAddLearnerNode, id));
        }

        return new Tuple<>(out, in);
    }

    private static Tuple<Config, ProgressMap> chain(Changer chg, List<Function<Changer, Tuple<Config, ProgressMap>>> ops) {
        for (Function<Changer, Tuple<Config, ProgressMap>> op : ops) {
            Tuple<Config, ProgressMap> t = op.apply(chg);
            Config cfg = t.getFirst();
            ProgressMap prs = t.getSecond();
            chg.getTracker().setConfig(cfg);
            chg.getTracker().setProgress(prs);
        }
        return new Tuple<>(chg.getTracker().getConfig(), chg.getTracker().getProgress());
    }

    public static Tuple<Config, ProgressMap> restore(Changer chg, ConfState cs) {
        Tuple<List<ConfChangeSingle>, List<ConfChangeSingle>> t = toConfChangeSingle(cs);
        List<ConfChangeSingle> outgoing = t.getFirst();
        List<ConfChangeSingle> incoming = t.getSecond();

        List<Function<Changer, Tuple<Config, ProgressMap>>> ops = new ArrayList<>();
        if (outgoing.size() == 0) {
            for (ConfChangeSingle cc : incoming) {
                ops.add(changer -> changer.simple(List.of(cc)));
            }
        } else {
            for (ConfChangeSingle cc : outgoing) {
                ops.add(changer -> changer.simple(List.of(cc)));
            }
            ops.add(changer -> changer.enterJoint(cs.autoLeave, incoming));
        }

        return chain(chg, ops);
    }

    public ConfState() {
        voters = new ArrayList<>();
        learners = new ArrayList<>();
        votersOutgoing = new ArrayList<>();
        learnersNext = new ArrayList<>();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        ConfState confState = (ConfState) o;
        this.voters = new ArrayList<>(this.voters);
        this.voters.sort(Long::compare);
        this.learners = new ArrayList<>(this.learners);
        this.learners.sort(Long::compare);
        this.votersOutgoing = new ArrayList<>(this.votersOutgoing);
        this.votersOutgoing.sort(Long::compare);
        this.learnersNext = new ArrayList<>(this.learnersNext);
        this.learnersNext.sort(Long::compare);

        confState.voters.sort(Long::compare);
        confState.learners.sort(Long::compare);
        confState.votersOutgoing.sort(Long::compare);
        confState.learnersNext.sort(Long::compare);

        return autoLeave == confState.autoLeave
                && Objects.equals(voters, confState.voters)
                && Objects.equals(learners, confState.learners)
                && Objects.equals(votersOutgoing, confState.votersOutgoing)
                && Objects.equals(learnersNext, confState.learnersNext);
    }

    @Override
    public int hashCode() {
        return Objects.hash(voters, learners, votersOutgoing, learnersNext, autoLeave);
    }

    public List<Long> getVoters() {
        return voters;
    }

    public void setVoters(List<Long> voters) {
        this.voters = new ArrayList<>(voters);
    }

    public List<Long> getLearners() {
        return learners;
    }

    public void setLearners(List<Long> learners) {
        this.learners = new ArrayList<>(learners);
    }

    public List<Long> getVotersOutgoing() {
        return votersOutgoing;
    }

    public void setVotersOutgoing(List<Long> votersOutgoing) {
        this.votersOutgoing = new ArrayList<>(votersOutgoing);
    }

    public List<Long> getLearnersNext() {
        return learnersNext;
    }

    public void setLearnersNext(List<Long> learnersNext) {
        this.learnersNext = new ArrayList<>(learnersNext);
    }

    public boolean isAutoLeave() {
        return autoLeave;
    }

    public void setAutoLeave(boolean autoLeave) {
        this.autoLeave = autoLeave;
    }

}
